# !/usr/bin/env python
# -*-coding:utf-8 -*-

"""
# File       : pwd_rule_check.py
# Time       ：2024/4/19 14:01
# Author     ：huchl
# version    ：python 3.6
# Description： 密码强度校验检查
"""

import re


class PasswordValidator(object):
    """
    一个用于验证密码是否满足特定安全要求的类。
    Attributes:usernames (list[str]): 统一认证用户名列表，用于验证密码是否与用户名相同。
    """

    def __init__(self, usernames=None):
        """
        初始化PasswordValidator对象。
        Args:usernames (list[str], optional): 统一认证用户名列表。默认为None，表示无用户名列表。
        """
        self.usernames = usernames or []

    def validate_length(self, password):
        """
        检查密码长度是否至少为8个字符。
        Args:password (str): 待验证的密码。
        Returns:tuple[bool, str]: 返回一个元组，第一个元素为布尔值，表示是否满足长度要求；第二个元素为错误消息（若不满足要求）。
        """
        if len(password) < 8:
            return False, "密码长度至少为8位"
        else:
            return True, ""

    def validate_startswith_alpha(self, password):
        """
        检查密码是否以字母开头。
        Args:password (str): 待验证的密码。
        Returns:tuple[bool, str]: 返回一个元组，第一个元素为布尔值，表示是否满足要求；第二个元素为错误消息（若不满足要求）。
        """
        if not password[0].isalpha():
            return False, "密码必须以字母开头"
        else:
            return True, ""

    def validate_three_of_four_categories(self, password):
        """
        检查密码是否至少包含大写字母、小写字母、数字和特殊字符中的三种类型。
        Args:password (str): 待验证的密码。
        Returns:tuple[bool, str]: 返回一个元组，第一个元素为布尔值，表示是否满足要求；第二个元素为错误消息（若不满足要求）。
        """
        categories_present = {
            'uppercase': any(c.isupper() for c in password),
            'lowercase': any(c.islower() for c in password),
            'digit': any(c.isdigit() for c in password),
            'special': any(c in '!@#$%^&*()_+{}[]|;:,.<>?/' for c in password),  # 常见特殊字符集合，可根据实际需求调整
        }

        num_categories_present = sum(categories_present.values())

        if num_categories_present < 3:
            return False, "密码必须包含大写字母、小写字母、数字和特殊字符中的至少三种类型"
        else:
            return True, ""

    def validate_contains_uppercase(self, password):
        """
        检查密码是否包含至少一个大写字母。
        Args:password (str): 待验证的密码。
        Returns:tuple[bool, str]: 返回一个元组，第一个元素为布尔值，表示是否满足要求；第二个元素为错误消息（若不满足要求）。
        """
        if not any(c.isupper() for c in password):
            return False, "密码必须包含大写字母"
        else:
            return True, ""

    def validate_contains_lowercase(self, password):
        """
        检查密码是否包含至少一个小写字母。
        Args:password (str): 待验证的密码。
        Returns:tuple[bool, str]: 返回一个元组，第一个元素为布尔值，表示是否满足要求；第二个元素为错误消息（若不满足要求）。
        """
        if not any(c.islower() for c in password):
            return False, "密码必须包含小写字母"
        else:
            return True, ""

    def validate_contains_special_char(self, password):
        """
        检查密码是否包含至少一个特殊字符（@或!）。
        Args:password (str): 待验证的密码。
        Returns:tuple[bool, str]: 返回一个元组，第一个元素为布尔值，表示是否满足要求；第二个元素为错误消息（若不满足要求）。
        """
        if not any(c in '!@#$%^&*()_+{}[]|;:,.<>?/' for c in password):
            return False, "密码必须包含特殊字符(@或!)"
        else:
            return True, ""

    def validate_not_username(self, password):
        """
        检查密码是否与统一认证用户名列表中的任何用户名相同。
        Args:password (str): 待验证的密码。
        Returns:tuple[bool, str]: 返回一个元组，第一个元素为布尔值，表示是否满足要求；第二个元素为错误消息（若不满足要求）。
        """
        if password in self.usernames:
            return False, "密码不能与统一认证用户名相同"
        else:
            return True, ""

    def validate_no_four_consecutive_same(self, password):
        """
        检查密码中是否包含四个连续相同的字符。
        Args:password (str): 待验证的密码。
        Returns:tuple[bool, str]: 返回一个元组，第一个元素为布尔值，表示是否满足要求；第二个元素为错误消息（若不满足要求）。
        """
        for i in range(len(password) - 3):
            if password[i:i + 4] == password[i] * 4:
                return False, "密码中不得包含四个连续相同的字符"
        else:
            return True, ""

    def _are_adjacent_keys(self, c1, c2):
        """
        判断两个字符是否为相邻的横排或斜排键盘按键。
        Args:c1 (str): 第一个字符。
            c2 (str): 第二个字符。
        Returns:bool: `True`表示相邻，`False`表示不相邻。
        """
        # 定义简化版QWERTY键盘布局（仅包含字母部分）
        keyboard_layout = [
            ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
            ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'],
            ['z', 'x', 'c', 'v', 'b', 'n', 'm']
        ]

        # 添加斜排关系（右上至左下对角线）
        diagonal_right_up = {'q': 'p', 'w': 'o', 'e': 'i', 'r': 'u', 't': 'y',
                             'a': 'l', 's': 'k', 'd': 'j', 'f': 'h', 'g': 'b',
                             'z': 'm', 'x': 'n'}

        # 查找字符在键盘布局中的位置
        try:
            row1, col1 = [row for row in keyboard_layout if c1 in row].index(c1), keyboard_layout.index([row for row in keyboard_layout if c1 in row])
            print(row1,col1)
            row2, col2 = [row for row in keyboard_layout if c2 in row].index(c2), keyboard_layout.index(
                [row for row in keyboard_layout if c2 in row])
            print(row2, col2)
        except ValueError:
            return False  # 字符不在键盘布局中，认为不相邻

        # 判断横排相邻
        if row1 == row2 and abs(col1 - col2) == 1:
            return True

        # 判断斜排相邻
        if c1 in diagonal_right_up and diagonal_right_up[c1] == c2:
            return True

        return False

    def validate_no_adjacent_keys(self, password):
        """
        检查密码中是否包含相邻横排或斜排的键盘按键组合。
        Args:password (str): 待验证的密码。
        Returns:tuple[bool, str]: 返回一个元组，第一个元素为布尔值，表示是否满足要求；第二个元素为错误消息（若不满足要求）。
        """
        for i in range(1, len(password)):
            if self._are_adjacent_keys(password[i - 1], password[i]):
                return False, "密码中不得包含相邻横排或斜排的键盘按键组合"

        return True, ""

    # def validate_not_common_pattern(self, password):
    #     """
    #     检查密码是否使用公开、易于破解的常见密码模式。
    #     Args:password (str): 待验证的密码。
    #     Returns:tuple[bool, str]: 返回一个元组，第一个元素为布尔值，表示是否满足要求；第二个元素为错误消息（若不满足要求）。
    #     """
    #     common_patterns = ['password', '123456', 'qwerty', 'admin', ...]
    #     if any(pattern in password.lower() for pattern in common_patterns):
    #         return False, "密码不得使用公开、易于破解的组合"
    #     else:
    #         return True, ""

    def validate_password(self, password):
        """
        调用所有验证方法，检查密码是否满足所有安全要求。
        Args:password (str): 待验证的密码。
        Returns:tuple[bool, str]: 返回一个元组，第一个元素为布尔值，表示密码是否符合要求；第二个元素为错误消息（若密码不符合要求）。
        """
        validation_methods = [
            self.validate_length,
            self.validate_startswith_alpha,
            self.validate_three_of_four_categories,
            self.validate_contains_uppercase,
            self.validate_contains_lowercase,
            self.validate_contains_special_char,
            self.validate_not_username,
            self.validate_no_four_consecutive_same,
            self.validate_no_adjacent_keys,
            # self.validate_not_common_pattern,
        ]

        for method in validation_methods:
            is_valid, message = method(password)
            if not is_valid:
                return False, message

        return True, "密码符合要求"


if __name__ == '__main__':
    code, msg = PasswordValidator().validate_no_adjacent_keys('wertyu')
    print(code,msg)
